package org.dromara.system.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import lombok.extern.slf4j.Slf4j;
import org.dromara.system.domain.Sms;
import org.dromara.system.domain.SmsCode;
import org.dromara.system.mapper.SmsMapper;
import org.dromara.system.service.SmsService;
import org.dromara.system.util.Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class SmsServiceImpl implements SmsService {


    private static final String REGION_ID = "cn-hangzhou"; //固定不用改
    private static final String PRODUCT = "Dysmsapi"; //固定不用改
    private static final String DOMAIN = "dysmsapi.aliyuncs.com"; //固定不用改

    @Value("${aliyun.sms.expire-minute:15}")
    private Integer expireMinute;
    @Value("${aliyun.sms.day-count:30}")
    private Integer dayCount;


    @Value("${aliyun.sms.sign.name:xxxxxx}")
    private String signName;
    @Value("${aliyun.sms.template.code:xxxxx}")
    private String templateCode;
    @Value("${aliyun.sms.accessKeyId:xxxxx}")
    private String accessKeyId;
    @Value("${aliyun.sms.accessKeySecret:xxxxx}")
    private String accessKeySecret;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private SmsMapper smsMapper;

    @Override
    public SmsCode generateCode(Sms sms) {
        String uuid = UUID.randomUUID().toString();
        String code = Util.randomCode(6);
        Map<String, String> map = new HashMap<>(2);
        map.put("code", code);
        map.put("phone", sms.getPhone());
        redisTemplate.opsForValue().set(smsRedisKey(uuid), JSONUtil.toJsonStr(map), expireMinute, TimeUnit.MINUTES);
        log.info("缓存验证码：{}", map);
        saveSmsAndSendCode(sms.getPhone(), code);
        SmsCode smsCode = new SmsCode();
        smsCode.setKey(uuid);
        return smsCode;
    }

    /**
     * 保存短信记录，并发送短信
     *
     * @param phone
     * @param code
     */
    private void saveSmsAndSendCode(String phone, String code) {
        checkTodaySendCount(phone);
        Sms sms = Sms.builder().phone(phone).build();
        Map<String, String> params = new HashMap<>();
        params.put("code", code);
        this.save(sms, params);
        CompletableFuture.runAsync(() -> {
            try {
                this.sendSmsMsg(sms);
            } catch (Exception e) {
                log.error("发送短信失败：{}", e.getMessage());
            }
        });

        // 当天发送验证码次数+1
        String countKey = countKey(phone);
        redisTemplate.opsForValue().increment(countKey, 1L);
        redisTemplate.expire(countKey, 1, TimeUnit.DAYS);
    }


    public SendSmsResponse sendSmsMsg(Sms sms) {
        SendSmsResponse response = null;
        try {
            sms.setSignName(signName);
            sms.setTemplateCode(templateCode);
            SendSmsRequest request = new SendSmsRequest();
            request.setMethod(MethodType.POST);
            request.setPhoneNumbers(sms.getPhone());
            request.setSignName(sms.getSignName());
            request.setTemplateCode(sms.getTemplateCode());
            request.setTemplateParam(sms.getSmsParams());
            IClientProfile profile = DefaultProfile.getProfile(REGION_ID, accessKeyId,
                accessKeySecret);
            DefaultProfile.addEndpoint(REGION_ID, REGION_ID, PRODUCT, DOMAIN);
            IAcsClient acsClient = new DefaultAcsClient(profile);
            response = acsClient.getAcsResponse(request);
            if (response != null) {
                log.info("发送短信结果：code:{}，message:{}，requestId:{}，bizId:{}", response.getCode(),
                    response.getMessage(),
                    response.getRequestId(), response.getBizId());
                sms.setCode(response.getCode());
                sms.setMessage(response.getMessage());
                sms.setBizId(response.getBizId());
            }
        } catch (ClientException ignored) {
        }
        update(sms);
        return response;
    }

    public void update(Sms sms) {
        smsMapper.updateById(sms);
    }


    public void save(Sms sms, Map<String, String> params) {
        if (ObjectUtil.isNotNull(params)) {
            sms.setSmsParams(JSONUtil.toJsonStr(params));
            sms.setDay(DateUtil.date());
        }
        smsMapper.insert(sms);
    }

    /**
     * 获取当天发送验证码次数 限制号码当天次数
     *
     * @param phone
     * @return
     */
    private void checkTodaySendCount(String phone) {
        String value = redisTemplate.opsForValue().get(countKey(phone));
        if (value != null) {
            Integer count = Integer.valueOf(value);
            if (count > dayCount) {
                throw new IllegalArgumentException("已超过当天最大次数");
            }
        }

    }

    private String countKey(String phone) {
        return "sms:count:" + LocalDate.now() + ":" + phone;
    }

    private String smsRedisKey(String str) {
        return "sms:" + str;
    }

    public String matchCodeAndGetPhone(String key, String code, Boolean delete, Integer second) {
        key = smsRedisKey(key);
        String value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            JSONObject json = JSONObject.parseObject(value);
            if (code != null && code.equals(json.getString("code"))) {
                log.info("验证码校验成功：{}", value);
                if (delete == null || delete) {
                    redisTemplate.delete(key);
                }
                if (Boolean.FALSE.equals(delete) && second != null && second > 0) {
                    redisTemplate.expire(key, second, TimeUnit.SECONDS);
                }
                return json.getString("phone");
            }
        }
        return null;
    }
}
